// Copyright (c) 2019 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_INJA_HPP_
#define INCLUDE_INJA_INJA_HPP_

#include <iostream>

#include <nlohmann/json.hpp>

// #include "environment.hpp"
// Copyright (c) 2019 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_
#define INCLUDE_INJA_ENVIRONMENT_HPP_

#include <fstream>
#include <memory>
#include <sstream>
#include <string>

#include <nlohmann/json.hpp>

// #include "config.hpp"
// Copyright (c) 2019 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_CONFIG_HPP_
#define INCLUDE_INJA_CONFIG_HPP_

#include <functional>
#include <string>

// #include "string_view.hpp"
// Copyright 2017-2019 by Martin Moene
//
// string-view lite, a C++17-like string_view for C++98 and later.
// For more information see https://github.com/martinmoene/string-view-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)



#ifndef NONSTD_SV_LITE_H_INCLUDED
#define NONSTD_SV_LITE_H_INCLUDED

#define string_view_lite_MAJOR 1
#define string_view_lite_MINOR 4
#define string_view_lite_PATCH 0

#define string_view_lite_VERSION                                                                                       \
  nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(                \
      string_view_lite_PATCH)

#define nssv_STRINGIFY(x) nssv_STRINGIFY_(x)
#define nssv_STRINGIFY_(x) #x

// string-view lite configuration:

#define nssv_STRING_VIEW_DEFAULT 0
#define nssv_STRING_VIEW_NONSTD 1
#define nssv_STRING_VIEW_STD 2

#if !defined(nssv_CONFIG_SELECT_STRING_VIEW)
#define nssv_CONFIG_SELECT_STRING_VIEW (nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD)
#endif

#if defined(nssv_CONFIG_SELECT_STD_STRING_VIEW) || defined(nssv_CONFIG_SELECT_NONSTD_STRING_VIEW)
#error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_...
#endif

#ifndef nssv_CONFIG_STD_SV_OPERATOR
#define nssv_CONFIG_STD_SV_OPERATOR 0
#endif

#ifndef nssv_CONFIG_USR_SV_OPERATOR
#define nssv_CONFIG_USR_SV_OPERATOR 1
#endif

#ifdef nssv_CONFIG_CONVERSION_STD_STRING
#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING
#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING
#endif

#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1
#endif

#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1
#endif

// Control presence of exception handling (try and auto discover):

#ifndef nssv_CONFIG_NO_EXCEPTIONS
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
#define nssv_CONFIG_NO_EXCEPTIONS 0
#else
#define nssv_CONFIG_NO_EXCEPTIONS 1
#endif
#endif

// C++ language version detection (C++20 is speculative):
// Note: VC14.0/1900 (VS2015) lacks too much from C++14.

#ifndef nssv_CPLUSPLUS
#if defined(_MSVC_LANG) && !defined(__clang__)
#define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)
#else
#define nssv_CPLUSPLUS __cplusplus
#endif
#endif

#define nssv_CPP98_OR_GREATER (nssv_CPLUSPLUS >= 199711L)
#define nssv_CPP11_OR_GREATER (nssv_CPLUSPLUS >= 201103L)
#define nssv_CPP11_OR_GREATER_ (nssv_CPLUSPLUS >= 201103L)
#define nssv_CPP14_OR_GREATER (nssv_CPLUSPLUS >= 201402L)
#define nssv_CPP17_OR_GREATER (nssv_CPLUSPLUS >= 201703L)
#define nssv_CPP20_OR_GREATER (nssv_CPLUSPLUS >= 202000L)

// use C++17 std::string_view if available and requested:

#if nssv_CPP17_OR_GREATER && defined(__has_include)
#if __has_include(<string_view> )
#define nssv_HAVE_STD_STRING_VIEW 1
#else
#define nssv_HAVE_STD_STRING_VIEW 0
#endif
#else
#define nssv_HAVE_STD_STRING_VIEW 0
#endif

#define nssv_USES_STD_STRING_VIEW                                                                                      \
  ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) ||                                                         \
   ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW))

#define nssv_HAVE_STARTS_WITH (nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW)
#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH

//
// Use C++17 std::string_view:
//

#if nssv_USES_STD_STRING_VIEW

#include <string_view>

// Extensions for std::string:

#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS

namespace nonstd {

template <class CharT, class Traits, class Allocator = std::allocator<CharT>>
std::basic_string<CharT, Traits, Allocator> to_string(std::basic_string_view<CharT, Traits> v,
                                                      Allocator const &a = Allocator()) {
  return std::basic_string<CharT, Traits, Allocator>(v.begin(), v.end(), a);
}

template <class CharT, class Traits, class Allocator>
std::basic_string_view<CharT, Traits> to_string_view(std::basic_string<CharT, Traits, Allocator> const &s) {
  return std::basic_string_view<CharT, Traits>(s.data(), s.size());
}

// Literal operators sv and _sv:

#if nssv_CONFIG_STD_SV_OPERATOR

using namespace std::literals::string_view_literals;

#endif

#if nssv_CONFIG_USR_SV_OPERATOR

inline namespace literals {
inline namespace string_view_literals {

constexpr std::string_view operator"" _sv(const char *str, size_t len) noexcept // (1)
{
  return std::string_view {str, len};
}

constexpr std::u16string_view operator"" _sv(const char16_t *str, size_t len) noexcept // (2)
{
  return std::u16string_view {str, len};
}

constexpr std::u32string_view operator"" _sv(const char32_t *str, size_t len) noexcept // (3)
{
  return std::u32string_view {str, len};
}

constexpr std::wstring_view operator"" _sv(const wchar_t *str, size_t len) noexcept // (4)
{
  return std::wstring_view {str, len};
}

} // namespace string_view_literals
} // namespace literals

#endif // nssv_CONFIG_USR_SV_OPERATOR

} // namespace nonstd

#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS

namespace nonstd {

using std::basic_string_view;
using std::string_view;
using std::u16string_view;
using std::u32string_view;
using std::wstring_view;

// literal "sv" and "_sv", see above

using std::operator==;
using std::operator!=;
using std::operator<;
using std::operator<=;
using std::operator>;
using std::operator>=;

using std::operator<<;

} // namespace nonstd

#else // nssv_HAVE_STD_STRING_VIEW

//
// Before C++17: use string_view lite:
//

// Compiler versions:
//
// MSVC++ 6.0  _MSC_VER == 1200 (Visual Studio 6.0)
// MSVC++ 7.0  _MSC_VER == 1300 (Visual Studio .NET 2002)
// MSVC++ 7.1  _MSC_VER == 1310 (Visual Studio .NET 2003)
// MSVC++ 8.0  _MSC_VER == 1400 (Visual Studio 2005)
// MSVC++ 9.0  _MSC_VER == 1500 (Visual Studio 2008)
// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017)

#if defined(_MSC_VER) && !defined(__clang__)
#define nssv_COMPILER_MSVC_VER (_MSC_VER)
#define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900)))
#else
#define nssv_COMPILER_MSVC_VER 0
#define nssv_COMPILER_MSVC_VERSION 0
#endif

#define nssv_COMPILER_VERSION(major, minor, patch) (10 * (10 * (major) + (minor)) + (patch))

#if defined(__clang__)
#define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
#else
#define nssv_COMPILER_CLANG_VERSION 0
#endif

#if defined(__GNUC__) && !defined(__clang__)
#define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
#else
#define nssv_COMPILER_GNUC_VERSION 0
#endif

// half-open range [lo..hi):
#define nssv_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi))

// Presence of language and library features:

#ifdef _HAS_CPP0X
#define nssv_HAS_CPP0X _HAS_CPP0X
#else
#define nssv_HAS_CPP0X 0
#endif

// Unless defined otherwise below, consider VC14 as C++11 for variant-lite:

#if nssv_COMPILER_MSVC_VER >= 1900
#undef nssv_CPP11_OR_GREATER
#define nssv_CPP11_OR_GREATER 1
#endif

#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500)
#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600)
#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700)
#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800)
#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900)
#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910)

#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER)
#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER)

// Presence of C++11 language features:

#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140
#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140
#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140
#define nssv_HAVE_NOEXCEPT nssv_CPP11_140
#define nssv_HAVE_NULLPTR nssv_CPP11_100
#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140
#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140
#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140
#define nssv_HAVE_WCHAR16_T nssv_CPP11_100
#define nssv_HAVE_WCHAR32_T nssv_CPP11_100

#if !((nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION) || nssv_BETWEEN(nssv_COMPILER_CLANG_VERSION, 300, 400))
#define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140
#else
#define nssv_HAVE_STD_DEFINED_LITERALS 0
#endif

// Presence of C++14 language features:

#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000

// Presence of C++17 language features:

#define nssv_HAVE_NODISCARD nssv_CPP17_000

// Presence of C++ library features:

#define nssv_HAVE_STD_HASH nssv_CPP11_120

// C++ feature usage:

#if nssv_HAVE_CONSTEXPR_11
#define nssv_constexpr constexpr
#else
#define nssv_constexpr /*constexpr*/
#endif

#if nssv_HAVE_CONSTEXPR_14
#define nssv_constexpr14 constexpr
#else
#define nssv_constexpr14 /*constexpr*/
#endif

#if nssv_HAVE_EXPLICIT_CONVERSION
#define nssv_explicit explicit
#else
#define nssv_explicit /*explicit*/
#endif

#if nssv_HAVE_INLINE_NAMESPACE
#define nssv_inline_ns inline
#else
#define nssv_inline_ns /*inline*/
#endif

#if nssv_HAVE_NOEXCEPT
#define nssv_noexcept noexcept
#else
#define nssv_noexcept /*noexcept*/
#endif

//#if nssv_HAVE_REF_QUALIFIER
//# define nssv_ref_qual  &
//# define nssv_refref_qual  &&
//#else
//# define nssv_ref_qual  /*&*/
//# define nssv_refref_qual  /*&&*/
//#endif

#if nssv_HAVE_NULLPTR
#define nssv_nullptr nullptr
#else
#define nssv_nullptr NULL
#endif

#if nssv_HAVE_NODISCARD
#define nssv_nodiscard [[nodiscard]]
#else
#define nssv_nodiscard /*[[nodiscard]]*/
#endif

// Additional includes:

#include <algorithm>
#include <cassert>
#include <iterator>
#include <limits>
#include <ostream>
#include <string> // std::char_traits<>

#if !nssv_CONFIG_NO_EXCEPTIONS
#include <stdexcept>
#endif

#if nssv_CPP11_OR_GREATER
#include <type_traits>
#endif

// Clang, GNUC, MSVC warning suppression macros:

#if defined(__clang__)
#pragma clang diagnostic ignored "-Wreserved-user-defined-literal"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wuser-defined-literals"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wliteral-suffix"
#endif // __clang__

#if nssv_COMPILER_MSVC_VERSION >= 140
#define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]]
#define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress : code))
#define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable : codes))
#else
#define nssv_SUPPRESS_MSGSL_WARNING(expr)
#define nssv_SUPPRESS_MSVC_WARNING(code, descr)
#define nssv_DISABLE_MSVC_WARNINGS(codes)
#endif

#if defined(__clang__)
#define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop")
#elif defined(__GNUC__)
#define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop")
#elif nssv_COMPILER_MSVC_VERSION >= 140
#define nssv_RESTORE_WARNINGS() __pragma(warning(pop))
#else
#define nssv_RESTORE_WARNINGS()
#endif

// Suppress the following MSVC (GSL) warnings:
// - C4455, non-gsl   : 'operator ""sv': literal suffix identifiers that do not
//                      start with an underscore are reserved
// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions;
//                      use brace initialization, gsl::narrow_cast or gsl::narow
// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead

nssv_DISABLE_MSVC_WARNINGS(4455 26481 26472)
    // nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" )
    // nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix )

    namespace nonstd {
  namespace sv_lite {

#if nssv_CPP11_OR_GREATER

  namespace detail {

  // Expect tail call optimization to make length() non-recursive:

  template <typename CharT> inline constexpr std::size_t length(CharT *s, std::size_t result = 0) {
    return *s == '\0' ? result : length(s + 1, result + 1);
  }

  } // namespace detail

#endif // nssv_CPP11_OR_GREATER

  template <class CharT, class Traits = std::char_traits<CharT>> class basic_string_view;

  //
  // basic_string_view:
  //

  template <class CharT, class Traits /* = std::char_traits<CharT> */
            >
  class basic_string_view {
  public:
    // Member types:

    typedef Traits traits_type;
    typedef CharT value_type;

    typedef CharT *pointer;
    typedef CharT const *const_pointer;
    typedef CharT &reference;
    typedef CharT const &const_reference;

    typedef const_pointer iterator;
    typedef const_pointer const_iterator;
    typedef std::reverse_iterator<const_iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

    // 24.4.2.1 Construction and assignment:

    nssv_constexpr basic_string_view() nssv_noexcept : data_(nssv_nullptr), size_(0) {}

#if nssv_CPP11_OR_GREATER
    nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept = default;
#else
    nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept : data_(other.data_),
                                                                                     size_(other.size_) {}
#endif

    nssv_constexpr basic_string_view(CharT const *s, size_type count) nssv_noexcept // non-standard noexcept
        : data_(s),
          size_(count) {}

    nssv_constexpr basic_string_view(CharT const *s) nssv_noexcept // non-standard noexcept
        : data_(s)
#if nssv_CPP17_OR_GREATER
        ,
          size_(Traits::length(s))
#elif nssv_CPP11_OR_GREATER
        ,
          size_(detail::length(s))
#else
        ,
          size_(Traits::length(s))
#endif
    {
    }

    // Assignment:

#if nssv_CPP11_OR_GREATER
    nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept = default;
#else
    nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept {
      data_ = other.data_;
      size_ = other.size_;
      return *this;
    }
#endif

    // 24.4.2.2 Iterator support:

    nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; }
    nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; }

    nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); }
    nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); }

    nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator(end()); }
    nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator(begin()); }

    nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); }
    nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); }

    // 24.4.2.3 Capacity:

    nssv_constexpr size_type size() const nssv_noexcept { return size_; }
    nssv_constexpr size_type length() const nssv_noexcept { return size_; }
    nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits<size_type>::max)(); }

    // since C++20
    nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept { return 0 == size_; }

    // 24.4.2.4 Element access:

    nssv_constexpr const_reference operator[](size_type pos) const { return data_at(pos); }

    nssv_constexpr14 const_reference at(size_type pos) const {
#if nssv_CONFIG_NO_EXCEPTIONS
      assert(pos < size());
#else
      if (pos >= size()) {
        throw std::out_of_range("nonstd::string_view::at()");
      }
#endif
      return data_at(pos);
    }

    nssv_constexpr const_reference front() const { return data_at(0); }
    nssv_constexpr const_reference back() const { return data_at(size() - 1); }

    nssv_constexpr const_pointer data() const nssv_noexcept { return data_; }

    // 24.4.2.5 Modifiers:

    nssv_constexpr14 void remove_prefix(size_type n) {
      assert(n <= size());
      data_ += n;
      size_ -= n;
    }

    nssv_constexpr14 void remove_suffix(size_type n) {
      assert(n <= size());
      size_ -= n;
    }

    nssv_constexpr14 void swap(basic_string_view &other) nssv_noexcept {
      using std::swap;
      swap(data_, other.data_);
      swap(size_, other.size_);
    }

    // 24.4.2.6 String operations:

    size_type copy(CharT *dest, size_type n, size_type pos = 0) const {
#if nssv_CONFIG_NO_EXCEPTIONS
      assert(pos <= size());
#else
      if (pos > size()) {
        throw std::out_of_range("nonstd::string_view::copy()");
      }
#endif
      const size_type rlen = (std::min)(n, size() - pos);

      (void)Traits::copy(dest, data() + pos, rlen);

      return rlen;
    }

    nssv_constexpr14 basic_string_view substr(size_type pos = 0, size_type n = npos) const {
#if nssv_CONFIG_NO_EXCEPTIONS
      assert(pos <= size());
#else
      if (pos > size()) {
        throw std::out_of_range("nonstd::string_view::substr()");
      }
#endif
      return basic_string_view(data() + pos, (std::min)(n, size() - pos));
    }

    // compare(), 6x:

    nssv_constexpr14 int compare(basic_string_view other) const nssv_noexcept // (1)
    {
      if (const int result = Traits::compare(data(), other.data(), (std::min)(size(), other.size()))) {
        return result;
      }

      return size() == other.size() ? 0 : size() < other.size() ? -1 : 1;
    }

    nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other) const // (2)
    {
      return substr(pos1, n1).compare(other);
    }

    nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other, size_type pos2,
                               size_type n2) const // (3)
    {
      return substr(pos1, n1).compare(other.substr(pos2, n2));
    }

    nssv_constexpr int compare(CharT const *s) const // (4)
    {
      return compare(basic_string_view(s));
    }

    nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s) const // (5)
    {
      return substr(pos1, n1).compare(basic_string_view(s));
    }

    nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s, size_type n2) const // (6)
    {
      return substr(pos1, n1).compare(basic_string_view(s, n2));
    }

    // 24.4.2.7 Searching:

    // starts_with(), 3x, since C++20:

    nssv_constexpr bool starts_with(basic_string_view v) const nssv_noexcept // (1)
    {
      return size() >= v.size() && compare(0, v.size(), v) == 0;
    }

    nssv_constexpr bool starts_with(CharT c) const nssv_noexcept // (2)
    {
      return starts_with(basic_string_view(&c, 1));
    }

    nssv_constexpr bool starts_with(CharT const *s) const // (3)
    {
      return starts_with(basic_string_view(s));
    }

    // ends_with(), 3x, since C++20:

    nssv_constexpr bool ends_with(basic_string_view v) const nssv_noexcept // (1)
    {
      return size() >= v.size() && compare(size() - v.size(), npos, v) == 0;
    }

    nssv_constexpr bool ends_with(CharT c) const nssv_noexcept // (2)
    {
      return ends_with(basic_string_view(&c, 1));
    }

    nssv_constexpr bool ends_with(CharT const *s) const // (3)
    {
      return ends_with(basic_string_view(s));
    }

    // find(), 4x:

    nssv_constexpr14 size_type find(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1)
    {
      return assert(v.size() == 0 || v.data() != nssv_nullptr),
             pos >= size() ? npos : to_pos(std::search(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq));
    }

    nssv_constexpr14 size_type find(CharT c, size_type pos = 0) const nssv_noexcept // (2)
    {
      return find(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr14 size_type find(CharT const *s, size_type pos, size_type n) const // (3)
    {
      return find(basic_string_view(s, n), pos);
    }

    nssv_constexpr14 size_type find(CharT const *s, size_type pos = 0) const // (4)
    {
      return find(basic_string_view(s), pos);
    }

    // rfind(), 4x:

    nssv_constexpr14 size_type rfind(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1)
    {
      if (size() < v.size()) {
        return npos;
      }

      if (v.empty()) {
        return (std::min)(size(), pos);
      }

      const_iterator last = cbegin() + (std::min)(size() - v.size(), pos) + v.size();
      const_iterator result = std::find_end(cbegin(), last, v.cbegin(), v.cend(), Traits::eq);

      return result != last ? size_type(result - cbegin()) : npos;
    }

    nssv_constexpr14 size_type rfind(CharT c, size_type pos = npos) const nssv_noexcept // (2)
    {
      return rfind(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr14 size_type rfind(CharT const *s, size_type pos, size_type n) const // (3)
    {
      return rfind(basic_string_view(s, n), pos);
    }

    nssv_constexpr14 size_type rfind(CharT const *s, size_type pos = npos) const // (4)
    {
      return rfind(basic_string_view(s), pos);
    }

    // find_first_of(), 4x:

    nssv_constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1)
    {
      return pos >= size() ? npos
                           : to_pos(std::find_first_of(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq));
    }

    nssv_constexpr size_type find_first_of(CharT c, size_type pos = 0) const nssv_noexcept // (2)
    {
      return find_first_of(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr size_type find_first_of(CharT const *s, size_type pos, size_type n) const // (3)
    {
      return find_first_of(basic_string_view(s, n), pos);
    }

    nssv_constexpr size_type find_first_of(CharT const *s, size_type pos = 0) const // (4)
    {
      return find_first_of(basic_string_view(s), pos);
    }

    // find_last_of(), 4x:

    nssv_constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1)
    {
      return empty() ? npos
                     : pos >= size() ? find_last_of(v, size() - 1)
                                     : to_pos(std::find_first_of(const_reverse_iterator(cbegin() + pos + 1), crend(),
                                                                 v.cbegin(), v.cend(), Traits::eq));
    }

    nssv_constexpr size_type find_last_of(CharT c, size_type pos = npos) const nssv_noexcept // (2)
    {
      return find_last_of(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr size_type find_last_of(CharT const *s, size_type pos, size_type count) const // (3)
    {
      return find_last_of(basic_string_view(s, count), pos);
    }

    nssv_constexpr size_type find_last_of(CharT const *s, size_type pos = npos) const // (4)
    {
      return find_last_of(basic_string_view(s), pos);
    }

    // find_first_not_of(), 4x:

    nssv_constexpr size_type find_first_not_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1)
    {
      return pos >= size() ? npos : to_pos(std::find_if(cbegin() + pos, cend(), not_in_view(v)));
    }

    nssv_constexpr size_type find_first_not_of(CharT c, size_type pos = 0) const nssv_noexcept // (2)
    {
      return find_first_not_of(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos, size_type count) const // (3)
    {
      return find_first_not_of(basic_string_view(s, count), pos);
    }

    nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos = 0) const // (4)
    {
      return find_first_not_of(basic_string_view(s), pos);
    }

    // find_last_not_of(), 4x:

    nssv_constexpr size_type find_last_not_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1)
    {
      return empty() ? npos
                     : pos >= size()
                           ? find_last_not_of(v, size() - 1)
                           : to_pos(std::find_if(const_reverse_iterator(cbegin() + pos + 1), crend(), not_in_view(v)));
    }

    nssv_constexpr size_type find_last_not_of(CharT c, size_type pos = npos) const nssv_noexcept // (2)
    {
      return find_last_not_of(basic_string_view(&c, 1), pos);
    }

    nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos, size_type count) const // (3)
    {
      return find_last_not_of(basic_string_view(s, count), pos);
    }

    nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos = npos) const // (4)
    {
      return find_last_not_of(basic_string_view(s), pos);
    }

    // Constants:

#if nssv_CPP17_OR_GREATER
    static nssv_constexpr size_type npos = size_type(-1);
#elif nssv_CPP11_OR_GREATER
    enum : size_type { npos = size_type(-1) };
#else
    enum { npos = size_type(-1) };
#endif

  private:
    struct not_in_view {
      const basic_string_view v;

      nssv_constexpr explicit not_in_view(basic_string_view v) : v(v) {}

      nssv_constexpr bool operator()(CharT c) const { return npos == v.find_first_of(c); }
    };

    nssv_constexpr size_type to_pos(const_iterator it) const { return it == cend() ? npos : size_type(it - cbegin()); }

    nssv_constexpr size_type to_pos(const_reverse_iterator it) const {
      return it == crend() ? npos : size_type(crend() - it - 1);
    }

    nssv_constexpr const_reference data_at(size_type pos) const {
#if nssv_BETWEEN(nssv_COMPILER_GNUC_VERSION, 1, 500)
      return data_[pos];
#else
      return assert(pos < size()), data_[pos];
#endif
    }

  private:
    const_pointer data_;
    size_type size_;

  public:
#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS

    template <class Allocator>
    basic_string_view(std::basic_string<CharT, Traits, Allocator> const &s) nssv_noexcept : data_(s.data()),
                                                                                            size_(s.size()) {}

#if nssv_HAVE_EXPLICIT_CONVERSION

    template <class Allocator> explicit operator std::basic_string<CharT, Traits, Allocator>() const {
      return to_string(Allocator());
    }

#endif // nssv_HAVE_EXPLICIT_CONVERSION

#if nssv_CPP11_OR_GREATER

    template <class Allocator = std::allocator<CharT>>
    std::basic_string<CharT, Traits, Allocator> to_string(Allocator const &a = Allocator()) const {
      return std::basic_string<CharT, Traits, Allocator>(begin(), end(), a);
    }

#else

    std::basic_string<CharT, Traits> to_string() const { return std::basic_string<CharT, Traits>(begin(), end()); }

    template <class Allocator> std::basic_string<CharT, Traits, Allocator> to_string(Allocator const &a) const {
      return std::basic_string<CharT, Traits, Allocator>(begin(), end(), a);
    }

#endif // nssv_CPP11_OR_GREATER

#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS
  };

  //
  // Non-member functions:
  //

  // 24.4.3 Non-member comparison functions:
  // lexicographically compare two string views (function template):

  template <class CharT, class Traits>
  nssv_constexpr bool operator==(basic_string_view<CharT, Traits> lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) == 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator!=(basic_string_view<CharT, Traits> lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) != 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<(basic_string_view<CharT, Traits> lhs,
                                basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) < 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<=(basic_string_view<CharT, Traits> lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) <= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>(basic_string_view<CharT, Traits> lhs,
                                basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) > 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>=(basic_string_view<CharT, Traits> lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) >= 0;
  }

  // Let S be basic_string_view<CharT, Traits>, and sv be an instance of S.
  // Implementations shall provide sufficient additional overloads marked
  // constexpr and noexcept so that an object t with an implicit conversion
  // to S can be compared according to Table 67.

#if !nssv_CPP11_OR_GREATER || nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 100, 141)

  // accomodate for older compilers:

  // ==

  template <class CharT, class Traits>
  nssv_constexpr bool operator==(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) == 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator==(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) == 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator==(basic_string_view<CharT, Traits> lhs,
                                 std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator==(std::basic_string<CharT, Traits> rhs,
                                 basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
  }

  // !=

  template <class CharT, class Traits>
  nssv_constexpr bool operator!=(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) != 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator!=(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) != 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator!=(basic_string_view<CharT, Traits> lhs,
                                 std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.size() != rhs.size() && lhs.compare(rhs) != 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator!=(std::basic_string<CharT, Traits> rhs,
                                 basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return lhs.size() != rhs.size() || rhs.compare(lhs) != 0;
  }

  // <

  template <class CharT, class Traits>
  nssv_constexpr bool operator<(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) < 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) > 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<(basic_string_view<CharT, Traits> lhs,
                                std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) < 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<(std::basic_string<CharT, Traits> rhs,
                                basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return rhs.compare(lhs) > 0;
  }

  // <=

  template <class CharT, class Traits>
  nssv_constexpr bool operator<=(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) <= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<=(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) >= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<=(basic_string_view<CharT, Traits> lhs,
                                 std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) <= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator<=(std::basic_string<CharT, Traits> rhs,
                                 basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return rhs.compare(lhs) >= 0;
  }

  // >

  template <class CharT, class Traits>
  nssv_constexpr bool operator>(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) > 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) < 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>(basic_string_view<CharT, Traits> lhs,
                                std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) > 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>(std::basic_string<CharT, Traits> rhs,
                                basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return rhs.compare(lhs) < 0;
  }

  // >=

  template <class CharT, class Traits>
  nssv_constexpr bool operator>=(basic_string_view<CharT, Traits> lhs, char const *rhs) nssv_noexcept {
    return lhs.compare(rhs) >= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>=(char const *lhs, basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return rhs.compare(lhs) <= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>=(basic_string_view<CharT, Traits> lhs,
                                 std::basic_string<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) >= 0;
  }

  template <class CharT, class Traits>
  nssv_constexpr bool operator>=(std::basic_string<CharT, Traits> rhs,
                                 basic_string_view<CharT, Traits> lhs) nssv_noexcept {
    return rhs.compare(lhs) <= 0;
  }

#else // newer compilers:

#define nssv_BASIC_STRING_VIEW_I(T, U) typename std::decay<basic_string_view<T, U>>::type

#if nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 140, 150)
#define nssv_MSVC_ORDER(x) , int = x
#else
#define nssv_MSVC_ORDER(x) /*, int=x*/
#endif

  // ==

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator==(basic_string_view<CharT, Traits> lhs,
                                 nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.compare(rhs) == 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator==(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
  }

  // !=

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator!=(basic_string_view<CharT, Traits> lhs,
                                 nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.size() != rhs.size() || lhs.compare(rhs) != 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator!=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) != 0;
  }

  // <

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator<(basic_string_view<CharT, Traits> lhs,
                                nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.compare(rhs) < 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator<(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) < 0;
  }

  // <=

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator<=(basic_string_view<CharT, Traits> lhs,
                                 nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.compare(rhs) <= 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator<=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) <= 0;
  }

  // >

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator>(basic_string_view<CharT, Traits> lhs,
                                nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.compare(rhs) > 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator>(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) > 0;
  }

  // >=

  template <class CharT, class Traits nssv_MSVC_ORDER(1)>
  nssv_constexpr bool operator>=(basic_string_view<CharT, Traits> lhs,
                                 nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept {
    return lhs.compare(rhs) >= 0;
  }

  template <class CharT, class Traits nssv_MSVC_ORDER(2)>
  nssv_constexpr bool operator>=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs,
                                 basic_string_view<CharT, Traits> rhs) nssv_noexcept {
    return lhs.compare(rhs) >= 0;
  }

#undef nssv_MSVC_ORDER
#undef nssv_BASIC_STRING_VIEW_I

#endif // compiler-dependent approach to comparisons

  // 24.4.4 Inserters and extractors:

  namespace detail {

  template <class Stream> void write_padding(Stream &os, std::streamsize n) {
    for (std::streamsize i = 0; i < n; ++i)
      os.rdbuf()->sputc(os.fill());
  }

  template <class Stream, class View> Stream &write_to_stream(Stream &os, View const &sv) {
    typename Stream::sentry sentry(os);

    if (!os)
      return os;

    const std::streamsize length = static_cast<std::streamsize>(sv.length());

    // Whether, and how, to pad:
    const bool pad = (length < os.width());
    const bool left_pad = pad && (os.flags() & std::ios_base::adjustfield) == std::ios_base::right;

    if (left_pad)
      write_padding(os, os.width() - length);

    // Write span characters:
    os.rdbuf()->sputn(sv.begin(), length);

    if (pad && !left_pad)
      write_padding(os, os.width() - length);

    // Reset output stream width:
    os.width(0);

    return os;
  }

  } // namespace detail

  template <class CharT, class Traits>
  std::basic_ostream<CharT, Traits> &operator<<(std::basic_ostream<CharT, Traits> &os,
                                                basic_string_view<CharT, Traits> sv) {
    return detail::write_to_stream(os, sv);
  }

  // Several typedefs for common character types are provided:

  typedef basic_string_view<char> string_view;
  typedef basic_string_view<wchar_t> wstring_view;
#if nssv_HAVE_WCHAR16_T
  typedef basic_string_view<char16_t> u16string_view;
  typedef basic_string_view<char32_t> u32string_view;
#endif

  } // namespace sv_lite
} // namespace nonstd::sv_lite

//
// 24.4.6 Suffix for basic_string_view literals:
//

#if nssv_HAVE_USER_DEFINED_LITERALS

namespace nonstd {
nssv_inline_ns namespace literals {
  nssv_inline_ns namespace string_view_literals {

#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS

    nssv_constexpr nonstd::sv_lite::string_view operator"" sv(const char *str, size_t len) nssv_noexcept // (1)
    {
      return nonstd::sv_lite::string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::u16string_view operator"" sv(const char16_t *str, size_t len) nssv_noexcept // (2)
    {
      return nonstd::sv_lite::u16string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::u32string_view operator"" sv(const char32_t *str, size_t len) nssv_noexcept // (3)
    {
      return nonstd::sv_lite::u32string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::wstring_view operator"" sv(const wchar_t *str, size_t len) nssv_noexcept // (4)
    {
      return nonstd::sv_lite::wstring_view {str, len};
    }

#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS

#if nssv_CONFIG_USR_SV_OPERATOR

    nssv_constexpr nonstd::sv_lite::string_view operator"" _sv(const char *str, size_t len) nssv_noexcept // (1)
    {
      return nonstd::sv_lite::string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::u16string_view operator"" _sv(const char16_t *str, size_t len) nssv_noexcept // (2)
    {
      return nonstd::sv_lite::u16string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::u32string_view operator"" _sv(const char32_t *str, size_t len) nssv_noexcept // (3)
    {
      return nonstd::sv_lite::u32string_view {str, len};
    }

    nssv_constexpr nonstd::sv_lite::wstring_view operator"" _sv(const wchar_t *str, size_t len) nssv_noexcept // (4)
    {
      return nonstd::sv_lite::wstring_view {str, len};
    }

#endif // nssv_CONFIG_USR_SV_OPERATOR
  }
}
} // namespace nonstd

#endif

//
// Extensions for std::string:
//

#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS

namespace nonstd {
namespace sv_lite {

// Exclude MSVC 14 (19.00): it yields ambiguous to_string():

#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140

template <class CharT, class Traits, class Allocator = std::allocator<CharT>>
std::basic_string<CharT, Traits, Allocator> to_string(basic_string_view<CharT, Traits> v,
                                                      Allocator const &a = Allocator()) {
  return std::basic_string<CharT, Traits, Allocator>(v.begin(), v.end(), a);
}

#else

template <class CharT, class Traits> std::basic_string<CharT, Traits> to_string(basic_string_view<CharT, Traits> v) {
  return std::basic_string<CharT, Traits>(v.begin(), v.end());
}

template <class CharT, class Traits, class Allocator>
std::basic_string<CharT, Traits, Allocator> to_string(basic_string_view<CharT, Traits> v, Allocator const &a) {
  return std::basic_string<CharT, Traits, Allocator>(v.begin(), v.end(), a);
}

#endif // nssv_CPP11_OR_GREATER

template <class CharT, class Traits, class Allocator>
basic_string_view<CharT, Traits> to_string_view(std::basic_string<CharT, Traits, Allocator> const &s) {
  return basic_string_view<CharT, Traits>(s.data(), s.size());
}

} // namespace sv_lite
} // namespace nonstd

#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS

//
// make types and algorithms available in namespace nonstd:
//

namespace nonstd {

using sv_lite::basic_string_view;
using sv_lite::string_view;
using sv_lite::wstring_view;

#if nssv_HAVE_WCHAR16_T
using sv_lite::u16string_view;
#endif
#if nssv_HAVE_WCHAR32_T
using sv_lite::u32string_view;
#endif

// literal "sv"

using sv_lite::operator==;
using sv_lite::operator!=;
using sv_lite::operator<;
using sv_lite::operator<=;
using sv_lite::operator>;
using sv_lite::operator>=;

using sv_lite::operator<<;

#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS
using sv_lite::to_string;
using sv_lite::to_string_view;
#endif

} // namespace nonstd

// 24.4.5 Hash support (C++11):

// Note: The hash value of a string view object is equal to the hash value of
// the corresponding string object.

#if nssv_HAVE_STD_HASH

#include <functional>

namespace std {

template <> struct hash<nonstd::string_view> {
public:
  std::size_t operator()(nonstd::string_view v) const nssv_noexcept {
    return std::hash<std::string>()(std::string(v.data(), v.size()));
  }
};

template <> struct hash<nonstd::wstring_view> {
public:
  std::size_t operator()(nonstd::wstring_view v) const nssv_noexcept {
    return std::hash<std::wstring>()(std::wstring(v.data(), v.size()));
  }
};

template <> struct hash<nonstd::u16string_view> {
public:
  std::size_t operator()(nonstd::u16string_view v) const nssv_noexcept {
    return std::hash<std::u16string>()(std::u16string(v.data(), v.size()));
  }
};

template <> struct hash<nonstd::u32string_view> {
public:
  std::size_t operator()(nonstd::u32string_view v) const nssv_noexcept {
    return std::hash<std::u32string>()(std::u32string(v.data(), v.size()));
  }
};

} // namespace std

#endif // nssv_HAVE_STD_HASH

nssv_RESTORE_WARNINGS()

#endif // nssv_HAVE_STD_STRING_VIEW
#endif // NONSTD_SV_LITE_H_INCLUDED


namespace inja {

/*!
 * \brief Class for lexer configuration.
 */
struct LexerConfig {
  std::string statement_open {"{%"};
  std::string statement_open_no_lstrip {"{%+"};
  std::string statement_open_force_lstrip {"{%-"};
  std::string statement_close {"%}"};
  std::string statement_close_force_rstrip {"-%}"};
  std::string line_statement {"##"};
  std::string expression_open {"{{"};
  std::string expression_close {"}}"};
  std::string comment_open {"{#"};
  std::string comment_close {"#}"};
  std::string open_chars {"#{"};

  bool trim_blocks {false};
  bool lstrip_blocks {false};

  void update_open_chars() {
    open_chars = "";
    if (open_chars.find(line_statement[0]) == std::string::npos) {
      open_chars += line_statement[0];
    }
    if (open_chars.find(statement_open[0]) == std::string::npos) {
      open_chars += statement_open[0];
    }
    if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) {
      open_chars += statement_open_no_lstrip[0];
    }
    if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) {
      open_chars += statement_open_force_lstrip[0];
    }
    if (open_chars.find(expression_open[0]) == std::string::npos) {
      open_chars += expression_open[0];
    }
    if (open_chars.find(comment_open[0]) == std::string::npos) {
      open_chars += comment_open[0];
    }
  }
};

/*!
 * \brief Class for parser configuration.
 */
struct ParserConfig {
  bool search_included_templates_in_files {true};
  /// Extra options for limiting included files' scope
  bool include_scope_limit = false;
  std::string include_scope = "templates";
};

/*!
 * \brief Class for render configuration.
 */
struct RenderConfig {
  bool throw_at_missing_includes {true};
};

} // namespace inja

#endif // INCLUDE_INJA_CONFIG_HPP_

// #include "function_storage.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_
#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_

#include <vector>

// #include "string_view.hpp"


namespace inja {

using json = nlohmann::json;

using Arguments = std::vector<const json *>;
using CallbackFunction = std::function<json(Arguments &args)>;

/*!
 * \brief Class for builtin functions and user-defined callbacks.
 */
class FunctionStorage {
public:
  enum class Operation {
    Not,
    And,
    Or,
    In,
    Equal,
    NotEqual,
    Greater,
    GreaterEqual,
    Less,
    LessEqual,
    Add,
    Subtract,
    Multiplication,
    Division,
    Power,
    Modulo,
    At,
    Default,
    DivisibleBy,
    Even,
    Exists,
    ExistsInObject,
    First,
    Float,
    Int,
    IsArray,
    IsBoolean,
    IsFloat,
    IsInteger,
    IsNumber,
    IsObject,
    IsString,
    Last,
    Length,
    Lower,
    Max,
    Min,
    Odd,
    Range,
    Round,
    Sort,
    Upper,
    Callback,
    ParenLeft,
    ParenRight,
    None,
  };

  const int VARIADIC {-1};

  struct FunctionData {
    Operation operation;

    CallbackFunction callback;
  };

  std::map<std::pair<std::string, int>, FunctionData> function_storage = {
    {std::make_pair("at", 2), FunctionData { Operation::At }},
    {std::make_pair("default", 2), FunctionData { Operation::Default }},
    {std::make_pair("divisibleBy", 2), FunctionData { Operation::DivisibleBy }},
    {std::make_pair("even", 1), FunctionData { Operation::Even }},
    {std::make_pair("exists", 1), FunctionData { Operation::Exists }},
    {std::make_pair("existsIn", 2), FunctionData { Operation::ExistsInObject }},
    {std::make_pair("first", 1), FunctionData { Operation::First }},
    {std::make_pair("float", 1), FunctionData { Operation::Float }},
    {std::make_pair("int", 1), FunctionData { Operation::Int }},
    {std::make_pair("isArray", 1), FunctionData { Operation::IsArray }},
    {std::make_pair("isBoolean", 1), FunctionData { Operation::IsBoolean }},
    {std::make_pair("isFloat", 1), FunctionData { Operation::IsFloat }},
    {std::make_pair("isInteger", 1), FunctionData { Operation::IsInteger }},
    {std::make_pair("isNumber", 1), FunctionData { Operation::IsNumber }},
    {std::make_pair("isObject", 1), FunctionData { Operation::IsObject }},
    {std::make_pair("isString", 1), FunctionData { Operation::IsString }},
    {std::make_pair("last", 1), FunctionData { Operation::Last }},
    {std::make_pair("length", 1), FunctionData { Operation::Length }},
    {std::make_pair("lower", 1), FunctionData { Operation::Lower }},
    {std::make_pair("max", 1), FunctionData { Operation::Max }},
    {std::make_pair("min", 1), FunctionData { Operation::Min }},
    {std::make_pair("odd", 1), FunctionData { Operation::Odd }},
    {std::make_pair("range", 1), FunctionData { Operation::Range }},
    {std::make_pair("round", 2), FunctionData { Operation::Round }},
    {std::make_pair("sort", 1), FunctionData { Operation::Sort }},
    {std::make_pair("upper", 1), FunctionData { Operation::Upper }},
  };

public:
  void add_builtin(nonstd::string_view name, int num_args, Operation op) {
    function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData { op });
  }

  void add_callback(nonstd::string_view name, int num_args, const CallbackFunction &callback) {
    function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData { Operation::Callback, callback });
  }

  FunctionData find_function(nonstd::string_view name, int num_args) const {
    auto it = function_storage.find(std::make_pair(static_cast<std::string>(name), num_args));
    if (it != function_storage.end()) {
      return it->second;

    // Find variadic function
    } else if (num_args > 0) {
      it = function_storage.find(std::make_pair(static_cast<std::string>(name), VARIADIC));
      if (it != function_storage.end()) {
        return it->second;
      }
    }

    return { Operation::None };
  }
};

} // namespace inja

#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_

// #include "parser.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_PARSER_HPP_
#define INCLUDE_INJA_PARSER_HPP_

#include <limits>
#include <stack>
#include <string>
#include <utility>
#include <queue>
#include <vector>

// #include "config.hpp"

// #include "exceptions.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_
#define INCLUDE_INJA_EXCEPTIONS_HPP_

#include <stdexcept>
#include <string>

namespace inja {

struct SourceLocation {
  size_t line;
  size_t column;
};

struct InjaError : public std::runtime_error {
  std::string type;
  std::string message;

  bool has_location {false};
  SourceLocation location;

  InjaError(const std::string &type, const std::string &message)
      : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message) {}

  InjaError(const std::string &type, const std::string &message, SourceLocation location)
      : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" +
                           std::to_string(location.column) + ") " + message),
        type(type), message(message), has_location(true), location(location) {}
};

struct ParserError : public InjaError {
  ParserError(const std::string &message) : InjaError("parser_error", message) {}
  ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {}
};

struct RenderError : public InjaError {
  RenderError(const std::string &message) : InjaError("render_error", message) {}
  RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {}
};

struct FileError : public InjaError {
  FileError(const std::string &message) : InjaError("file_error", message) {}
  FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {}
};

struct JsonError : public InjaError {
  JsonError(const std::string &message) : InjaError("json_error", message) {}
  JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {}
};

} // namespace inja

#endif // INCLUDE_INJA_EXCEPTIONS_HPP_

// #include "function_storage.hpp"

// #include "lexer.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_LEXER_HPP_
#define INCLUDE_INJA_LEXER_HPP_

#include <cctype>
#include <locale>

// #include "config.hpp"

// #include "token.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_TOKEN_HPP_
#define INCLUDE_INJA_TOKEN_HPP_

#include <string>

// #include "string_view.hpp"


namespace inja {

/*!
 * \brief Helper-class for the inja Lexer.
 */
struct Token {
  enum class Kind {
    Text,
    ExpressionOpen,     // {{
    ExpressionClose,    // }}
    LineStatementOpen,  // ##
    LineStatementClose, // \n
    StatementOpen,      // {%
    StatementClose,     // %}
    CommentOpen,        // {#
    CommentClose,       // #}
    Id,                 // this, this.foo
    Number,             // 1, 2, -1, 5.2, -5.3
    String,             // "this"
    Plus,               // +
    Minus,              // -
    Times,              // *
    Slash,              // /
    Percent,            // %
    Power,              // ^
    Comma,              // ,
    Colon,              // :
    LeftParen,          // (
    RightParen,         // )
    LeftBracket,        // [
    RightBracket,       // ]
    LeftBrace,          // {
    RightBrace,         // }
    Equal,              // ==
    NotEqual,           // !=
    GreaterThan,        // >
    GreaterEqual,       // >=
    LessThan,           // <
    LessEqual,          // <=
    Unknown,
    Eof,
  };

  Kind kind {Kind::Unknown};
  nonstd::string_view text;

  explicit constexpr Token() = default;
  explicit constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {}

  std::string describe() const {
    switch (kind) {
    case Kind::Text:
      return "<text>";
    case Kind::LineStatementClose:
      return "<eol>";
    case Kind::Eof:
      return "<eof>";
    default:
      return static_cast<std::string>(text);
    }
  }
};

} // namespace inja

#endif // INCLUDE_INJA_TOKEN_HPP_

// #include "utils.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_UTILS_HPP_
#define INCLUDE_INJA_UTILS_HPP_

#include <algorithm>
#include <fstream>
#include <string>
#include <utility>

// #include "exceptions.hpp"

// #include "string_view.hpp"


namespace inja {

inline void open_file_or_throw(const std::string &path, std::ifstream &file) {
  file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    file.open(path);
  } catch (const std::ios_base::failure & /*e*/) {
    throw FileError("failed accessing file at '" + path + "'");
  }
}

namespace string_view {
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
  start = std::min(start, view.size());
  end = std::min(std::max(start, end), view.size());
  return view.substr(start, end - start);
}

inline std::pair<nonstd::string_view, nonstd::string_view> split(nonstd::string_view view, char Separator) {
  size_t idx = view.find(Separator);
  if (idx == nonstd::string_view::npos) {
    return std::make_pair(view, nonstd::string_view());
  }
  return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos));
}

inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) {
  return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0);
}
} // namespace string_view

inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) {
  // Get line and offset position (starts at 1:1)
  auto sliced = string_view::slice(content, 0, pos);
  std::size_t last_newline = sliced.rfind("\n");

  if (last_newline == nonstd::string_view::npos) {
    return {1, sliced.length() + 1};
  }

  // Count newlines
  size_t count_lines = 0;
  size_t search_start = 0;
  while (search_start <= sliced.size()) {
    search_start = sliced.find("\n", search_start) + 1;
    if (search_start <= 0) {
      break;
    }
    count_lines += 1;
  }

  return {count_lines + 1, sliced.length() - last_newline};
}

} // namespace inja

#endif // INCLUDE_INJA_UTILS_HPP_


namespace inja {

/*!
 * \brief Class for lexing an inja Template.
 */
class Lexer {
  enum class State {
    Text,
    ExpressionStart,
    ExpressionBody,
    LineStart,
    LineBody,
    StatementStart,
    StatementStartNoLstrip,
    StatementStartForceLstrip,
    StatementBody,
    CommentStart,
    CommentBody,
  };

  enum class MinusState {
    Operator,
    Number,
  };

  const LexerConfig &config;

  State state;
  MinusState minus_state;
  nonstd::string_view m_in;
  size_t tok_start;
  size_t pos;
  bool temp_trim_flag = false;


  Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) {
  again:
    // skip whitespace (except for \n as it might be a close)
    if (tok_start >= m_in.size()) {
      return make_token(Token::Kind::Eof);
    }
    char ch = m_in[tok_start];
    if (ch == ' ' || ch == '\t' || ch == '\r') {
      tok_start += 1;
      goto again;
    }

    // check for close
    if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) {
      state = State::Text;
      pos = tok_start + close_trim.size();
      Token tok = make_token(closeKind);
      skip_whitespaces_and_newlines();
      return tok;
    }

    if (ch == '-') {
      if (inja::string_view::starts_with(m_in.substr(tok_start + 1), close)) {
        tok_start += 1;
        temp_trim_flag = true;
      } else
        return make_token(Token::Kind::Unknown);
    }

    if (inja::string_view::starts_with(m_in.substr(tok_start), close)) {
      state = State::Text;
      pos = tok_start + close.size();
      Token tok = make_token(closeKind);
      if (trim || temp_trim_flag) {
        skip_whitespaces_and_first_newline();
      }
      temp_trim_flag = false;
      return tok;
    }

    // skip \n
    if (ch == '\n') {
      tok_start += 1;
      goto again;
    }

    pos = tok_start + 1;
    if (std::isalpha(ch)) {
      minus_state = MinusState::Operator;
      return scan_id();
    }

    MinusState current_minus_state = minus_state;
    if (minus_state == MinusState::Operator) {
      minus_state = MinusState::Number;
    }

    switch (ch) {
    case '+':
      return make_token(Token::Kind::Plus);
    case '-':
      if (current_minus_state == MinusState::Operator) {
        return make_token(Token::Kind::Minus);
      }
      return scan_number();
    case '*':
      return make_token(Token::Kind::Times);
    case '/':
      return make_token(Token::Kind::Slash);
    case '^':
      return make_token(Token::Kind::Power);
    case '%':
      return make_token(Token::Kind::Percent);
    case ',':
      return make_token(Token::Kind::Comma);
    case ':':
      return make_token(Token::Kind::Colon);
    case '(':
      return make_token(Token::Kind::LeftParen);
    case ')':
      minus_state = MinusState::Operator;
      return make_token(Token::Kind::RightParen);
    case '[':
      return make_token(Token::Kind::LeftBracket);
    case ']':
      minus_state = MinusState::Operator;
      return make_token(Token::Kind::RightBracket);
    case '{':
      return make_token(Token::Kind::LeftBrace);
    case '}':
      minus_state = MinusState::Operator;
      return make_token(Token::Kind::RightBrace);
    case '>':
      if (pos < m_in.size() && m_in[pos] == '=') {
        pos += 1;
        return make_token(Token::Kind::GreaterEqual);
      }
      return make_token(Token::Kind::GreaterThan);
    case '<':
      if (pos < m_in.size() && m_in[pos] == '=') {
        pos += 1;
        return make_token(Token::Kind::LessEqual);
      }
      return make_token(Token::Kind::LessThan);
    case '=':
      if (pos < m_in.size() && m_in[pos] == '=') {
        pos += 1;
        return make_token(Token::Kind::Equal);
      }
      return make_token(Token::Kind::Unknown);
    case '!':
      if (pos < m_in.size() && m_in[pos] == '=') {
        pos += 1;
        return make_token(Token::Kind::NotEqual);
      }
      return make_token(Token::Kind::Unknown);
    case '\"':
      return scan_string();
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      minus_state = MinusState::Operator;
      return scan_number();
    case '_':
      minus_state = MinusState::Operator;
      return scan_id();
    default:
      return make_token(Token::Kind::Unknown);
    }
  }

  Token scan_id() {
    for (;;) {
      if (pos >= m_in.size()) {
        break;
      }
      char ch = m_in[pos];
      if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') {
        break;
      }
      pos += 1;
    }
    return make_token(Token::Kind::Id);
  }

  Token scan_number() {
    for (;;) {
      if (pos >= m_in.size()) {
        break;
      }
      char ch = m_in[pos];
      // be very permissive in lexer (we'll catch errors when conversion happens)
      if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') {
        break;
      }
      pos += 1;
    }
    return make_token(Token::Kind::Number);
  }

  Token scan_string() {
    bool escape {false};
    for (;;) {
      if (pos >= m_in.size()) {
        break;
      }
      char ch = m_in[pos++];
      if (ch == '\\') {
        escape = true;
      } else if (!escape && ch == m_in[tok_start]) {
        break;
      } else {
        escape = false;
      }
    }
    return make_token(Token::Kind::String);
  }

  Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, tok_start, pos)); }

  void skip_whitespaces_and_newlines() {
    if (pos < m_in.size()) {
      while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) {
        pos += 1;
      }
    }
  }

  void skip_whitespaces_and_first_newline() {
    if (pos < m_in.size()) {
      while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) {
        pos += 1;
      }
    }

    if (pos < m_in.size()) {
      char ch = m_in[pos];
      if (ch == '\n') {
        pos += 1;
      } else if (ch == '\r') {
        pos += 1;
        if (pos < m_in.size() && m_in[pos] == '\n') {
          pos += 1;
        }
      }
    }
  }

  static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) {
    nonstd::string_view result = text;
    while (!result.empty()) {
      char ch = result.back();
      if (ch == ' ' || ch == '\t') {
        result.remove_suffix(1);
      } else if (ch == '\n' || ch == '\r') {
        break;
      } else {
        return text;
      }
    }
    return result;
  }

public:
  explicit Lexer(const LexerConfig &config) : config(config) {}

  SourceLocation current_position() const {
    return get_source_location(m_in, tok_start);
  }

  void start(nonstd::string_view input) {
    m_in = input;
    tok_start = 0;
    pos = 0;
    state = State::Text;
    minus_state = MinusState::Number;
  }

  Token scan() {
    tok_start = pos;

  again:
    if (tok_start >= m_in.size()) {
      return make_token(Token::Kind::Eof);
    }

    switch (state) {
    default:
    case State::Text: {
      // fast-scan to first open character
      size_t open_start = m_in.substr(pos).find_first_of(config.open_chars);
      if (open_start == nonstd::string_view::npos) {
        // didn't find open, return remaining text as text token
        pos = m_in.size();
        return make_token(Token::Kind::Text);
      }
      pos += open_start;

      // try to match one of the opening sequences, and get the close
      nonstd::string_view open_str = m_in.substr(pos);
      bool must_lstrip = false;
      if (inja::string_view::starts_with(open_str, config.expression_open)) {
        state = State::ExpressionStart;
      } else if (inja::string_view::starts_with(open_str, config.statement_open)) {
        if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) {
          state = State::StatementStartNoLstrip;
        } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip )) {
          state = State::StatementStartForceLstrip;
          must_lstrip = true;
        } else {
          state = State::StatementStart;
          must_lstrip = config.lstrip_blocks;
        }
      } else if (inja::string_view::starts_with(open_str, config.comment_open)) {
        state = State::CommentStart;
        must_lstrip = config.lstrip_blocks;
      } else if ((pos == 0 || m_in[pos - 1] == '\n') &&
                 inja::string_view::starts_with(open_str, config.line_statement)) {
        state = State::LineStart;
      } else {
        pos += 1; // wasn't actually an opening sequence
        goto again;
      }

      nonstd::string_view text = string_view::slice(m_in, tok_start, pos);
      if (must_lstrip) {
        text = clear_final_line_if_whitespace(text);
      }

      if (text.empty()) {
        goto again; // don't generate empty token
      }
      return Token(Token::Kind::Text, text);
    }
    case State::ExpressionStart: {
      state = State::ExpressionBody;
      pos += config.expression_open.size();
      // whitespace control
      if (m_in[pos] == '-') {
        pos += 1;
        temp_trim_flag = true;
      }
      return make_token(Token::Kind::ExpressionOpen);
    }
    case State::LineStart: {
      state = State::LineBody;
      pos += config.line_statement.size();
      return make_token(Token::Kind::LineStatementOpen);
    }
    case State::StatementStart: {
      state = State::StatementBody;
      pos += config.statement_open.size();
      return make_token(Token::Kind::StatementOpen);
    }
    case State::StatementStartNoLstrip: {
      state = State::StatementBody;
      pos += config.statement_open_no_lstrip.size();
      return make_token(Token::Kind::StatementOpen);
    }
    case State::StatementStartForceLstrip: {
      state = State::StatementBody;
      pos += config.statement_open_force_lstrip.size();
      return make_token(Token::Kind::StatementOpen);
    }
    case State::CommentStart: {
      state = State::CommentBody;
      pos += config.comment_open.size();
      return make_token(Token::Kind::CommentOpen);
    }
    case State::ExpressionBody:
      return scan_body(config.expression_close, Token::Kind::ExpressionClose);
    case State::LineBody:
      return scan_body("\n", Token::Kind::LineStatementClose);
    case State::StatementBody:
      return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks);
    case State::CommentBody: {
      // fast-scan to comment close
      size_t end = m_in.substr(pos).find(config.comment_close);
      if (end == nonstd::string_view::npos) {
        pos = m_in.size();
        return make_token(Token::Kind::Eof);
      }
      // return the entire comment in the close token
      state = State::Text;
      pos += end + config.comment_close.size();
      Token tok = make_token(Token::Kind::CommentClose);
      if (config.trim_blocks) {
        skip_whitespaces_and_first_newline();
      }
      return tok;
    }
    }
  }

  const LexerConfig &get_config() const {
    return config;
  }
};

} // namespace inja

#endif // INCLUDE_INJA_LEXER_HPP_

// #include "node.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_NODE_HPP_
#define INCLUDE_INJA_NODE_HPP_

#include <string>
#include <utility>

#include <nlohmann/json.hpp>

// #include "function_storage.hpp"

// #include "string_view.hpp"



namespace inja {

class NodeVisitor;
class BlockNode;
class TextNode;
class ExpressionNode;
class LiteralNode;
class JsonNode;
class FunctionNode;
class ExpressionListNode;
class StatementNode;
class ForStatementNode;
class ForArrayStatementNode;
class ForObjectStatementNode;
class IfStatementNode;
class IncludeStatementNode;


class NodeVisitor {
public:
  virtual void visit(const BlockNode& node) = 0;
  virtual void visit(const TextNode& node) = 0;
  virtual void visit(const ExpressionNode& node) = 0;
  virtual void visit(const LiteralNode& node) = 0;
  virtual void visit(const JsonNode& node) = 0;
  virtual void visit(const FunctionNode& node) = 0;
  virtual void visit(const ExpressionListNode& node) = 0;
  virtual void visit(const StatementNode& node) = 0;
  virtual void visit(const ForStatementNode& node) = 0;
  virtual void visit(const ForArrayStatementNode& node) = 0;
  virtual void visit(const ForObjectStatementNode& node) = 0;
  virtual void visit(const IfStatementNode& node) = 0;
  virtual void visit(const IncludeStatementNode& node) = 0;
};

/*!
 * \brief Base node class for the abstract syntax tree (AST).
 */
class AstNode {
public:
  virtual void accept(NodeVisitor& v) const = 0;

  size_t pos;

  AstNode(size_t pos) : pos(pos) { }
  virtual ~AstNode() { };
};


class BlockNode : public AstNode {
public:
  std::vector<std::shared_ptr<AstNode>> nodes;

  explicit BlockNode() : AstNode(0) {}

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class TextNode : public AstNode {
public:
  std::string content;

  explicit TextNode(nonstd::string_view content, size_t pos): AstNode(pos), content(content) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class ExpressionNode : public AstNode {
public:
  explicit ExpressionNode(size_t pos) : AstNode(pos) {}

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class LiteralNode : public ExpressionNode {
public:
  nlohmann::json value;

  explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class JsonNode : public ExpressionNode {
public:
  std::string name;
  std::string ptr {""};

  explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name) {
    // Convert dot notation to json pointer notation
    do {
      nonstd::string_view part;
      std::tie(part, ptr_name) = string_view::split(ptr_name, '.');
      ptr.push_back('/');
      ptr.append(part.begin(), part.end());
    } while (!ptr_name.empty());
  }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class FunctionNode : public ExpressionNode {
  using Op = FunctionStorage::Operation;

public:
  enum class Associativity {
    Left,
    Right,
  };

  unsigned int precedence;
  Associativity associativity;

  Op operation;

  std::string name;
  size_t number_args;
  CallbackFunction callback;

  explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(5), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { }
  explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) {
    switch (operation) {
      case Op::Not: {
        precedence = 4;
        associativity = Associativity::Left;
      } break;
      case Op::And: {
        precedence = 1;
        associativity = Associativity::Left;
      } break;
      case Op::Or: {
        precedence = 1;
        associativity = Associativity::Left;
      } break;
      case Op::In: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::Equal: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::NotEqual: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::Greater: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::GreaterEqual: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::Less: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::LessEqual: {
        precedence = 2;
        associativity = Associativity::Left;
      } break;
      case Op::Add: {
        precedence = 3;
        associativity = Associativity::Left;
      } break;
      case Op::Subtract: {
        precedence = 3;
        associativity = Associativity::Left;
      } break;
      case Op::Multiplication: {
        precedence = 4;
        associativity = Associativity::Left;
      } break;
      case Op::Division: {
        precedence = 4;
        associativity = Associativity::Left;
      } break;
      case Op::Power: {
        precedence = 5;
        associativity = Associativity::Right;
      } break;
      case Op::Modulo: {
        precedence = 4;
        associativity = Associativity::Left;
      } break;
      default: {
        precedence = 1;
        associativity = Associativity::Left;
      }
    }
  }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class ExpressionListNode : public AstNode {
public:
  std::vector<std::shared_ptr<ExpressionNode>> rpn_output;

  explicit ExpressionListNode() : AstNode(0) { }
  explicit ExpressionListNode(size_t pos) : AstNode(pos) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class StatementNode : public AstNode {
public:
  StatementNode(size_t pos) : AstNode(pos) { }

  virtual void accept(NodeVisitor& v) const = 0;
};

class ForStatementNode : public StatementNode {
public:
  ExpressionListNode condition;
  BlockNode body;
  BlockNode *parent;

  ForStatementNode(size_t pos) : StatementNode(pos) { }

  virtual void accept(NodeVisitor& v) const = 0;
};

class ForArrayStatementNode : public ForStatementNode {
public:
  nonstd::string_view value;

  explicit ForArrayStatementNode(nonstd::string_view value, size_t pos) : ForStatementNode(pos), value(value) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class ForObjectStatementNode : public ForStatementNode {
public:
  nonstd::string_view key;
  nonstd::string_view value;

  explicit ForObjectStatementNode(nonstd::string_view key, nonstd::string_view value, size_t pos) : ForStatementNode(pos), key(key), value(value) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class IfStatementNode : public StatementNode {
public:
  ExpressionListNode condition;
  BlockNode true_statement;
  BlockNode false_statement;
  BlockNode *parent;

  bool is_nested;
  bool has_false_statement {false};

  explicit IfStatementNode(size_t pos) : StatementNode(pos), is_nested(false) { }
  explicit IfStatementNode(bool is_nested, size_t pos) : StatementNode(pos), is_nested(is_nested) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  }
};

class IncludeStatementNode : public StatementNode {
public:
  std::string file;

  explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }

  void accept(NodeVisitor& v) const {
    v.visit(*this);
  };
};

} // namespace inja

#endif // INCLUDE_INJA_NODE_HPP_

// #include "template.hpp"
// Copyright (c) 2019 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_TEMPLATE_HPP_
#define INCLUDE_INJA_TEMPLATE_HPP_

#include <map>
#include <memory>
#include <string>
#include <vector>

// #include "node.hpp"

// #include "statistics.hpp"
// Copyright (c) 2019 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_STATISTICS_HPP_
#define INCLUDE_INJA_STATISTICS_HPP_

// #include "node.hpp"



namespace inja {

/*!
 * \brief A class for counting statistics on a Template.
 */
class StatisticsVisitor : public NodeVisitor {
  void visit(const BlockNode& node) {
    for (auto& n : node.nodes) {
      n->accept(*this);
    }
  }

  void visit(const TextNode&) { }
  void visit(const ExpressionNode&) { }
  void visit(const LiteralNode&) { }

  void visit(const JsonNode&) {
    variable_counter += 1;
  }

  void visit(const FunctionNode&) { }

  void visit(const ExpressionListNode& node) {
    for (auto& n : node.rpn_output) {
      n->accept(*this);
    }
  }

  void visit(const StatementNode&) { }
  void visit(const ForStatementNode&) { }

  void visit(const ForArrayStatementNode& node) {
    node.condition.accept(*this);
    node.body.accept(*this);
  }

  void visit(const ForObjectStatementNode& node) {
    node.condition.accept(*this);
    node.body.accept(*this);
  }

  void visit(const IfStatementNode& node) {
    node.condition.accept(*this);
    node.true_statement.accept(*this);
    node.false_statement.accept(*this);
  }

  void visit(const IncludeStatementNode&) { }

public:
  unsigned int variable_counter;

  explicit StatisticsVisitor() : variable_counter(0) { }
};

} // namespace inja

#endif // INCLUDE_INJA_STATISTICS_HPP_



namespace inja {

/*!
 * \brief The main inja Template.
 */
struct Template {
  BlockNode root;
  std::string content;

  explicit Template() { }
  explicit Template(const std::string& content): content(content) { }

  /// Return number of variables (total number, not distinct ones) in the template
  int count_variables() {
    auto statistic_visitor = StatisticsVisitor();
    root.accept(statistic_visitor);
    return statistic_visitor.variable_counter;
  }
};

using TemplateStorage = std::map<std::string, Template>;

} // namespace inja

#endif // INCLUDE_INJA_TEMPLATE_HPP_

// #include "token.hpp"

// #include "utils.hpp"


#include <nlohmann/json.hpp>

namespace inja {

/*!
 * \brief Class for parsing an inja Template.
 */
class Parser {
  const ParserConfig &config;

  Lexer lexer;
  TemplateStorage &template_storage;
  const FunctionStorage &function_storage;

  Token tok, peek_tok;
  bool have_peek_tok {false};

  size_t current_paren_level {0};
  size_t current_bracket_level {0};
  size_t current_brace_level {0};

  nonstd::string_view json_literal_start;

  BlockNode *current_block {nullptr};
  ExpressionListNode *current_expression_list {nullptr};
  std::stack<std::pair<FunctionNode*, size_t>> function_stack;

  std::stack<std::shared_ptr<FunctionNode>> operator_stack;
  std::stack<IfStatementNode*> if_statement_stack;
  std::stack<ForStatementNode*> for_statement_stack;

  void throw_parser_error(const std::string &message) {
    throw ParserError(message, lexer.current_position());
  }

  void get_next_token() {
    if (have_peek_tok) {
      tok = peek_tok;
      have_peek_tok = false;
    } else {
      tok = lexer.scan();
    }
  }

  void get_peek_token() {
    if (!have_peek_tok) {
      peek_tok = lexer.scan();
      have_peek_tok = true;
    }
  }

  void add_json_literal(const char* content_ptr) {
    nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size());
    current_expression_list->rpn_output.emplace_back(std::make_shared<LiteralNode>(json::parse(json_text), json_text.data() - content_ptr));
  }

  bool parse_expression(Template &tmpl, Token::Kind closing) {
    while (tok.kind != closing && tok.kind != Token::Kind::Eof) {
      // Literals
      switch (tok.kind) {
      case Token::Kind::String: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          json_literal_start = tok.text;
          add_json_literal(tmpl.content.c_str());
        }

      } break;
      case Token::Kind::Number: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          json_literal_start = tok.text;
          add_json_literal(tmpl.content.c_str());
        }

      } break;
      case Token::Kind::LeftBracket: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          json_literal_start = tok.text;
        }
        current_bracket_level += 1;

      } break;
      case Token::Kind::LeftBrace: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          json_literal_start = tok.text;
        }
        current_brace_level += 1;

      } break;
      case Token::Kind::RightBracket: {
        if (current_bracket_level == 0) {
          throw_parser_error("unexpected ']'");
        }

        current_bracket_level -= 1;
        if (current_brace_level == 0 && current_bracket_level == 0) {
          add_json_literal(tmpl.content.c_str());
        }

      } break;
      case Token::Kind::RightBrace: {
        if (current_brace_level == 0) {
          throw_parser_error("unexpected '}'");
        }

        current_brace_level -= 1;
        if (current_brace_level == 0 && current_bracket_level == 0) {
          add_json_literal(tmpl.content.c_str());
        }

      } break;
      case Token::Kind::Id: {
        get_peek_token();

        // Json Literal
        if (tok.text == static_cast<decltype(tok.text)>("true") || tok.text == static_cast<decltype(tok.text)>("false") || tok.text == static_cast<decltype(tok.text)>("null")) {
          if (current_brace_level == 0 && current_bracket_level == 0) {
            json_literal_start = tok.text;
            add_json_literal(tmpl.content.c_str());
          }

        // Functions
        } else if (peek_tok.kind == Token::Kind::LeftParen) {
          operator_stack.emplace(std::make_shared<FunctionNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
          function_stack.emplace(operator_stack.top().get(), current_paren_level);

        // Operator
        } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") {
          goto parse_operator;

        // Variables
        } else {
          current_expression_list->rpn_output.emplace_back(std::make_shared<JsonNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
        }

      // Operators
      } break;
      case Token::Kind::Equal:
      case Token::Kind::NotEqual:
      case Token::Kind::GreaterThan:
      case Token::Kind::GreaterEqual:
      case Token::Kind::LessThan:
      case Token::Kind::LessEqual:
      case Token::Kind::Plus:
      case Token::Kind::Minus:
      case Token::Kind::Times:
      case Token::Kind::Slash:
      case Token::Kind::Power:
      case Token::Kind::Percent: {

  parse_operator:
        FunctionStorage::Operation operation;
        switch (tok.kind) {
        case Token::Kind::Id: {
          if (tok.text == "and") {
            operation = FunctionStorage::Operation::And;
          } else if (tok.text == "or") {
            operation = FunctionStorage::Operation::Or;
          } else if (tok.text == "in") {
            operation = FunctionStorage::Operation::In;
          } else if (tok.text == "not") {
            operation = FunctionStorage::Operation::Not;
          } else {
            throw_parser_error("unknown operator in parser.");
          }
        } break;
        case Token::Kind::Equal: {
          operation = FunctionStorage::Operation::Equal;
        } break;
        case Token::Kind::NotEqual: {
          operation = FunctionStorage::Operation::NotEqual;
        } break;
        case Token::Kind::GreaterThan: {
          operation = FunctionStorage::Operation::Greater;
        } break;
        case Token::Kind::GreaterEqual: {
          operation = FunctionStorage::Operation::GreaterEqual;
        } break;
        case Token::Kind::LessThan: {
          operation = FunctionStorage::Operation::Less;
        } break;
        case Token::Kind::LessEqual: {
          operation = FunctionStorage::Operation::LessEqual;
        } break;
        case Token::Kind::Plus: {
          operation = FunctionStorage::Operation::Add;
        } break;
        case Token::Kind::Minus: {
          operation = FunctionStorage::Operation::Subtract;
        } break;
        case Token::Kind::Times: {
          operation = FunctionStorage::Operation::Multiplication;
        } break;
        case Token::Kind::Slash: {
          operation = FunctionStorage::Operation::Division;
        } break;
        case Token::Kind::Power: {
          operation = FunctionStorage::Operation::Power;
        } break;
        case Token::Kind::Percent: {
          operation = FunctionStorage::Operation::Modulo;
        } break;
        default: {
          throw_parser_error("unknown operator in parser.");
        }
        }
        auto function_node = std::make_shared<FunctionNode>(operation, tok.text.data() - tmpl.content.c_str());

        while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) {
          current_expression_list->rpn_output.emplace_back(operator_stack.top());
          operator_stack.pop();
        }

        operator_stack.emplace(function_node);

      } break;
      case Token::Kind::Comma: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          if (function_stack.empty()) {
            throw_parser_error("unexpected ','");
          }

          function_stack.top().first->number_args += 1;
        }

      } break;
      case Token::Kind::Colon: {
        if (current_brace_level == 0 && current_bracket_level == 0) {
          throw_parser_error("unexpected ':'");
        }

      } break;
      case Token::Kind::LeftParen: {
        current_paren_level += 1;
        operator_stack.emplace(std::make_shared<FunctionNode>(FunctionStorage::Operation::ParenLeft, tok.text.data() - tmpl.content.c_str()));

        get_peek_token();
        if (peek_tok.kind == Token::Kind::RightParen) {
          if (!function_stack.empty() && function_stack.top().second == current_paren_level - 1) {
            function_stack.top().first->number_args = 0;
          }
        }

      } break;
      case Token::Kind::RightParen: {
        current_paren_level -= 1;
        while (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) {
          current_expression_list->rpn_output.emplace_back(operator_stack.top());
          operator_stack.pop();
        }

        if (operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) {
          operator_stack.pop();
        }

        if (!function_stack.empty() && function_stack.top().second == current_paren_level) {
          auto func = function_stack.top().first;
          auto function_data = function_storage.find_function(func->name, func->number_args);
          if (function_data.operation == FunctionStorage::Operation::None) {
            throw_parser_error("unknown function " + func->name);
          }
          func->operation = function_data.operation;
          if (function_data.operation == FunctionStorage::Operation::Callback) {
            func->callback = function_data.callback;
          }

          function_stack.pop();
        }
      }
      default:
        break;
      }

      get_next_token();
    }

    while (!operator_stack.empty()) {
      current_expression_list->rpn_output.emplace_back(operator_stack.top());
      operator_stack.pop();
    }

    return true;
  }

  bool parse_statement(Template &tmpl, Token::Kind closing, nonstd::string_view path) {
    if (tok.kind != Token::Kind::Id) {
      return false;
    }

    if (tok.text == static_cast<decltype(tok.text)>("if")) {
      get_next_token();

      auto if_statement_node = std::make_shared<IfStatementNode>(tok.text.data() - tmpl.content.c_str());
      current_block->nodes.emplace_back(if_statement_node);
      if_statement_node->parent = current_block;
      if_statement_stack.emplace(if_statement_node.get());
      current_block = &if_statement_node->true_statement;
      current_expression_list = &if_statement_node->condition;

      if (!parse_expression(tmpl, closing)) {
        return false;
      }

    } else if (tok.text == static_cast<decltype(tok.text)>("else")) {
      if (if_statement_stack.empty()) {
        throw_parser_error("else without matching if");
      }
      auto &if_statement_data = if_statement_stack.top();
      get_next_token();

      if_statement_data->has_false_statement = true;
      current_block = &if_statement_data->false_statement;

      // Chained else if
      if (tok.kind == Token::Kind::Id && tok.text == static_cast<decltype(tok.text)>("if")) {
        get_next_token();

        auto if_statement_node = std::make_shared<IfStatementNode>(true, tok.text.data() - tmpl.content.c_str());
        current_block->nodes.emplace_back(if_statement_node);
        if_statement_node->parent = current_block;
        if_statement_stack.emplace(if_statement_node.get());
        current_block = &if_statement_node->true_statement;
        current_expression_list = &if_statement_node->condition;

        if (!parse_expression(tmpl, closing)) {
          return false;
        }
      }

    } else if (tok.text == static_cast<decltype(tok.text)>("endif")) {
      if (if_statement_stack.empty()) {
        throw_parser_error("endif without matching if");
      }

      // Nested if statements
      while (if_statement_stack.top()->is_nested) {
        if_statement_stack.pop();
      }

      auto &if_statement_data = if_statement_stack.top();
      get_next_token();

      current_block = if_statement_data->parent;
      if_statement_stack.pop();

    } else if (tok.text == static_cast<decltype(tok.text)>("for")) {
      get_next_token();

      // options: for a in arr; for a, b in obj
      if (tok.kind != Token::Kind::Id) {
        throw_parser_error("expected id, got '" + tok.describe() + "'");
      }

      Token value_token = tok;
      get_next_token();

      // Object type
      std::shared_ptr<ForStatementNode> for_statement_node;
      if (tok.kind == Token::Kind::Comma) {
        get_next_token();
        if (tok.kind != Token::Kind::Id) {
          throw_parser_error("expected id, got '" + tok.describe() + "'");
        }

        Token key_token = std::move(value_token);
        value_token = tok;
        get_next_token();

        for_statement_node = std::make_shared<ForObjectStatementNode>(key_token.text, value_token.text, tok.text.data() - tmpl.content.c_str());

      // Array type
      } else {
        for_statement_node = std::make_shared<ForArrayStatementNode>(value_token.text, tok.text.data() - tmpl.content.c_str());
      }

      current_block->nodes.emplace_back(for_statement_node);
      for_statement_node->parent = current_block;
      for_statement_stack.emplace(for_statement_node.get());
      current_block = &for_statement_node->body;
      current_expression_list = &for_statement_node->condition;

      if (tok.kind != Token::Kind::Id || tok.text != static_cast<decltype(tok.text)>("in")) {
        throw_parser_error("expected 'in', got '" + tok.describe() + "'");
      }
      get_next_token();

      if (!parse_expression(tmpl, closing)) {
        return false;
      }

    } else if (tok.text == static_cast<decltype(tok.text)>("endfor")) {
      if (for_statement_stack.empty()) {
        throw_parser_error("endfor without matching for");
      }

      auto &for_statement_data = for_statement_stack.top();
      get_next_token();

      current_block = for_statement_data->parent;
      for_statement_stack.pop();

    } else if (tok.text == static_cast<decltype(tok.text)>("include")) {
      get_next_token();

      if (tok.kind != Token::Kind::String) {
        throw_parser_error("expected string, got '" + tok.describe() + "'");
      }

      // Build the relative path
      json json_name = json::parse(tok.text);
      std::string pathname = static_cast<std::string>(path);
      pathname += json_name.get_ref<const std::string &>();
      if (pathname.compare(0, 2, "./") == 0) {
        pathname.erase(0, 2);
      }
      // sys::path::remove_dots(pathname, true, sys::path::Style::posix);

      if(config.include_scope_limit)
      {
#ifdef _WIN32
        if(pathname.find(":\\") != pathname.npos || pathname.find("..") != pathname.npos)
          throw FileError("access denied when trying to include '" + pathname + "': out of scope");
#else
        if(pathname.find("/") == 0 || pathname.find("..") != pathname.npos)
          throw FileError("access denied when trying to include '" + pathname + "': out of scope");
#endif // _WIN32
        if(config.include_scope.size() && pathname.find(config.include_scope) != 0)
          throw FileError("access denied when trying to include '" + pathname + "': out of scope");
      }

      if (config.search_included_templates_in_files && template_storage.find(pathname) == template_storage.end()) {
        auto include_template = Template(load_file(pathname));
        template_storage.emplace(pathname, include_template);
        parse_into_template(template_storage[pathname], pathname);
      }

      current_block->nodes.emplace_back(std::make_shared<IncludeStatementNode>(pathname, tok.text.data() - tmpl.content.c_str()));

      get_next_token();

    } else {
      return false;
    }
    return true;
  }

  void parse_into(Template &tmpl, nonstd::string_view path) {
    lexer.start(tmpl.content);
    current_block = &tmpl.root;

    for (;;) {
      get_next_token();
      switch (tok.kind) {
      case Token::Kind::Eof: {
        if (!if_statement_stack.empty()) {
          throw_parser_error("unmatched if");
        }
        if (!for_statement_stack.empty()) {
          throw_parser_error("unmatched for");
        }
      } return;
      case Token::Kind::Text: {
        current_block->nodes.emplace_back(std::make_shared<TextNode>(tok.text, tok.text.data() - tmpl.content.c_str()));
      } break;
      case Token::Kind::StatementOpen: {
        get_next_token();
        if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) {
          throw_parser_error("expected statement, got '" + tok.describe() + "'");
        }
        if (tok.kind != Token::Kind::StatementClose) {
          throw_parser_error("expected statement close, got '" + tok.describe() + "'");
        }
      } break;
      case Token::Kind::LineStatementOpen: {
        get_next_token();
        if (!parse_statement(tmpl, Token::Kind::LineStatementClose, path)) {
          throw_parser_error("expected statement, got '" + tok.describe() + "'");
        }
        if (tok.kind != Token::Kind::LineStatementClose && tok.kind != Token::Kind::Eof) {
          throw_parser_error("expected line statement close, got '" + tok.describe() + "'");
        }
      } break;
      case Token::Kind::ExpressionOpen: {
        get_next_token();

        auto expression_list_node = std::make_shared<ExpressionListNode>(tok.text.data() - tmpl.content.c_str());
        current_block->nodes.emplace_back(expression_list_node);
        current_expression_list = expression_list_node.get();

        if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) {
          throw_parser_error("expected expression, got '" + tok.describe() + "'");
        }

        if (tok.kind != Token::Kind::ExpressionClose) {
          throw_parser_error("expected expression close, got '" + tok.describe() + "'");
        }
      } break;
      case Token::Kind::CommentOpen: {
        get_next_token();
        if (tok.kind != Token::Kind::CommentClose) {
          throw_parser_error("expected comment close, got '" + tok.describe() + "'");
        }
      } break;
      default: {
        throw_parser_error("unexpected token '" + tok.describe() + "'");
      } break;
      }
    }
  }


public:
  explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config,
                  TemplateStorage &template_storage, const FunctionStorage &function_storage)
      : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) { }

  Template parse(nonstd::string_view input, nonstd::string_view path) {
    auto result = Template(static_cast<std::string>(input));
    parse_into(result, path);
    return result;
  }

  Template parse(nonstd::string_view input) {
    return parse(input, "./");
  }

  void parse_into_template(Template& tmpl, nonstd::string_view filename) {
    nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1);

    // StringRef path = sys::path::parent_path(filename);
    auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage);
    sub_parser.parse_into(tmpl, path);
  }

  std::string load_file(nonstd::string_view filename) {
    std::ifstream file;
    open_file_or_throw(static_cast<std::string>(filename), file);
    std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    return text;
  }
};

} // namespace inja

#endif // INCLUDE_INJA_PARSER_HPP_

// #include "renderer.hpp"
// Copyright (c) 2020 Pantor. All rights reserved.

#ifndef INCLUDE_INJA_RENDERER_HPP_
#define INCLUDE_INJA_RENDERER_HPP_

#include <algorithm>
#include <numeric>
#include <string>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>

// #include "config.hpp"

// #include "exceptions.hpp"

// #include "node.hpp"

// #include "template.hpp"

// #include "utils.hpp"


namespace inja {

/*!
 * \brief Class for rendering a Template with data.
 */
class Renderer : public NodeVisitor  {
  using Op = FunctionStorage::Operation;

  const RenderConfig config;
  const Template *current_template;
  const TemplateStorage &template_storage;
  const FunctionStorage &function_storage;

  const json *json_input;
  std::ostream *output_stream;

  json json_loop_data;
  json* current_loop_data = &json_loop_data["loop"];

  std::vector<std::shared_ptr<json>> json_tmp_stack;
  std::stack<const json*> json_eval_stack;
  std::stack<const JsonNode*> not_found_stack;

  bool truthy(const json* data) const {
    if (data->empty()) {
      return false;
    } else if (data->is_number()) {
      return (*data != 0);
    } else if (data->is_string()) {
      return !data->empty();
    }

    try {
      return data->get<bool>();
    } catch (json::type_error &e) {
      throw JsonError(e.what());
    }
  }

  void print_json(const json* value) {
    if (value->is_string()) {
      *output_stream << value->get_ref<const std::string &>();
    } else {
      *output_stream << value->dump();
    }
  }

  const std::shared_ptr<json> eval_expression_list(const ExpressionListNode& expression_list) {
    for (auto& expression : expression_list.rpn_output) {
      expression->accept(*this);
    }

    if (json_eval_stack.empty()) {
      throw_renderer_error("empty expression", expression_list);
    }

    if (json_eval_stack.size() != 1) {
      throw_renderer_error("malformed expression", expression_list);
    }

    auto result = json_eval_stack.top();
    json_eval_stack.pop();

    if (!result) {
      if (not_found_stack.empty()) {
        throw_renderer_error("expression could not be evaluated", expression_list);
      }

      auto node = not_found_stack.top();
      not_found_stack.pop();

      throw_renderer_error("variable '" + static_cast<std::string>(node->name) + "' not found", *node);
    }
    return std::make_shared<json>(*result);
  }

  void throw_renderer_error(const std::string &message, const AstNode& node) {
    SourceLocation loc = get_source_location(current_template->content, node.pos);
    throw RenderError(message, loc);
  }

  template<size_t N, bool throw_not_found=true>
  std::array<const json*, N> get_arguments(const AstNode& node) {
    if (json_eval_stack.size() < N) {
      throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node);
    }

    std::array<const json*, N> result;
    for (size_t i = 0; i < N; i += 1) {
      result[N - i - 1] = json_eval_stack.top();
      json_eval_stack.pop();

      if (!result[N - i - 1]) {
        auto json_node = not_found_stack.top();
        not_found_stack.pop();

        if (throw_not_found) {
          throw_renderer_error("variable '" + static_cast<std::string>(json_node->name) + "' not found", *json_node);
        }
      }
    }
    return result;
  }

  template<bool throw_not_found=true>
  Arguments get_argument_vector(size_t N, const AstNode& node) {
    Arguments result {N};
    for (size_t i = 0; i < N; i += 1) {
      result[N - i - 1] = json_eval_stack.top();
      json_eval_stack.pop();

      if (!result[N - i - 1]) {
        auto json_node = not_found_stack.top();
        not_found_stack.pop();

        if (throw_not_found) {
          throw_renderer_error("variable '" + static_cast<std::string>(json_node->name) + "' not found", *json_node);
        }
      }
    }
    return result;
  }

  void visit(const BlockNode& node) {
    for (auto& n : node.nodes) {
      n->accept(*this);
    }
  }

  void visit(const TextNode& node) {
    *output_stream << node.content;
  }

  void visit(const ExpressionNode&) { }

  void visit(const LiteralNode& node) {
    json_eval_stack.push(&node.value);
  }

  void visit(const JsonNode& node) {
    auto ptr = json::json_pointer(node.ptr);

    try {
      // First try to evaluate as a loop variable
      if (json_loop_data.contains(ptr)) {
        json_eval_stack.push(&json_loop_data.at(ptr));
      } else {
        json_eval_stack.push(&json_input->at(ptr));
      }

    } catch (std::exception &) {
      // Try to evaluate as a no-argument callback
      auto function_data = function_storage.find_function(node.name, 0);
      if (function_data.operation == FunctionStorage::Operation::Callback) {
        Arguments empty_args {};
        auto value = std::make_shared<json>(function_data.callback(empty_args));
        json_tmp_stack.push_back(value);
        json_eval_stack.push(value.get());

      } else {
        json_eval_stack.push(nullptr);
        not_found_stack.emplace(&node);
      }
    }
  }

  void visit(const FunctionNode& node) {
    std::shared_ptr<json> result_ptr;

    switch (node.operation) {
    case Op::Not: {
      auto args = get_arguments<1>(node);
      result_ptr = std::make_shared<json>(!truthy(args[0]));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::And: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(truthy(args[0]) && truthy(args[1]));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Or: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(truthy(args[0]) || truthy(args[1]));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::In: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Equal: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] == *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::NotEqual: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] != *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Greater: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] > *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::GreaterEqual: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] >= *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Less: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] < *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::LessEqual: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(*args[0] <= *args[1]);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Add: {
      auto args = get_arguments<2>(node);
      if (args[0]->is_string() && args[1]->is_string()) {
        result_ptr = std::make_shared<json>(args[0]->get<std::string>() + args[1]->get<std::string>());
        json_tmp_stack.push_back(result_ptr);
      } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
        result_ptr = std::make_shared<json>(args[0]->get<int>() + args[1]->get<int>());
        json_tmp_stack.push_back(result_ptr);
      } else {
        result_ptr = std::make_shared<json>(args[0]->get<double>() + args[1]->get<double>());
        json_tmp_stack.push_back(result_ptr);
      }
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Subtract: {
      auto args = get_arguments<2>(node);
      if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
        result_ptr = std::make_shared<json>(args[0]->get<int>() - args[1]->get<int>());
        json_tmp_stack.push_back(result_ptr);
      } else {
        result_ptr = std::make_shared<json>(args[0]->get<double>() - args[1]->get<double>());
        json_tmp_stack.push_back(result_ptr);
      }
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Multiplication: {
      auto args = get_arguments<2>(node);
      if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
        result_ptr = std::make_shared<json>(args[0]->get<int>() * args[1]->get<int>());
        json_tmp_stack.push_back(result_ptr);
      } else {
        result_ptr = std::make_shared<json>(args[0]->get<double>() * args[1]->get<double>());
        json_tmp_stack.push_back(result_ptr);
      }
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Division: {
      auto args = get_arguments<2>(node);
      if (args[1]->get<double>() == 0) {
        throw_renderer_error("division by zero", node);
      }
      result_ptr = std::make_shared<json>(args[0]->get<double>() / args[1]->get<double>());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Power: {
      auto args = get_arguments<2>(node);
      if (args[0]->is_number_integer() && args[1]->get<int>() >= 0) {
        int result = std::pow(args[0]->get<int>(), args[1]->get<int>());
        result_ptr = std::make_shared<json>(std::move(result));
        json_tmp_stack.push_back(result_ptr);
      } else {
        double result = std::pow(args[0]->get<int>(), args[1]->get<int>());
        result_ptr = std::make_shared<json>(std::move(result));
        json_tmp_stack.push_back(result_ptr);
      }
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Modulo: {
      auto args = get_arguments<2>(node);
      result_ptr = std::make_shared<json>(args[0]->get<int>() % args[1]->get<int>());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::At: {
      auto args = get_arguments<2>(node);
      json_eval_stack.push(&args[0]->at(args[1]->get<int>()));
    } break;
    case Op::Default: {
      auto default_arg = get_arguments<1>(node)[0];
      auto test_arg = get_arguments<1, false>(node)[0];
      json_eval_stack.push(test_arg ? test_arg : default_arg);
    } break;
    case Op::DivisibleBy: {
      auto args = get_arguments<2>(node);
      int divisor = args[1]->get<int>();
      result_ptr = std::make_shared<json>((divisor != 0) && (args[0]->get<int>() % divisor == 0));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Even: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<int>() % 2 == 0);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Exists: {
      auto &&name = get_arguments<1>(node)[0]->get_ref<const std::string &>();
      result_ptr = std::make_shared<json>(json_input->contains(json::json_pointer(JsonNode(name, 0).ptr)));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::ExistsInObject: {
      auto args = get_arguments<2>(node);
      auto &&name = args[1]->get_ref<const std::string &>();
      result_ptr = std::make_shared<json>(args[0]->find(name) != args[0]->end());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::First: {
      auto result = &get_arguments<1>(node)[0]->front();
      json_eval_stack.push(result);
    } break;
    case Op::Float: {
      result_ptr = std::make_shared<json>(std::stod(get_arguments<1>(node)[0]->get_ref<const std::string &>()));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Int: {
      result_ptr = std::make_shared<json>(std::stoi(get_arguments<1>(node)[0]->get_ref<const std::string &>()));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Last: {
      auto result = &get_arguments<1>(node)[0]->back();
      json_eval_stack.push(result);
    } break;
    case Op::Length: {
      auto val = get_arguments<1>(node)[0];
      if (val->is_string()) {
        result_ptr = std::make_shared<json>(val->get_ref<const std::string &>().length());
      } else {
        result_ptr = std::make_shared<json>(val->size());
      }
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Lower: {
      std::string result = get_arguments<1>(node)[0]->get<std::string>();
      std::transform(result.begin(), result.end(), result.begin(), ::tolower);
      result_ptr = std::make_shared<json>(std::move(result));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Max: {
      auto args = get_arguments<1>(node);
      auto result = std::max_element(args[0]->begin(), args[0]->end());
      json_eval_stack.push(&(*result));
    } break;
    case Op::Min: {
      auto args = get_arguments<1>(node);
      auto result = std::min_element(args[0]->begin(), args[0]->end());
      json_eval_stack.push(&(*result));
    } break;
    case Op::Odd: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<int>() % 2 != 0);
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Range: {
      std::vector<int> result(get_arguments<1>(node)[0]->get<int>());
      std::iota(result.begin(), result.end(), 0);
      result_ptr = std::make_shared<json>(std::move(result));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Round: {
      auto args = get_arguments<2>(node);
      int precision = args[1]->get<int>();
      double result = std::round(args[0]->get<double>() * std::pow(10.0, precision)) / std::pow(10.0, precision);
      result_ptr = std::make_shared<json>(std::move(result));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Sort: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<std::vector<json>>());
      std::sort(result_ptr->begin(), result_ptr->end());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Upper: {
      std::string result = get_arguments<1>(node)[0]->get<std::string>();
      std::transform(result.begin(), result.end(), result.begin(), ::toupper);
      result_ptr = std::make_shared<json>(std::move(result));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsBoolean: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_boolean());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsNumber: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsInteger: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number_integer());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsFloat: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number_float());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsObject: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_object());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsArray: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_array());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::IsString: {
      result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_string());
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::Callback: {
      auto args = get_argument_vector(node.number_args, node);
      result_ptr = std::make_shared<json>(node.callback(args));
      json_tmp_stack.push_back(result_ptr);
      json_eval_stack.push(result_ptr.get());
    } break;
    case Op::ParenLeft:
    case Op::ParenRight:
    case Op::None:
      break;
    }
  }

  void visit(const ExpressionListNode& node) {
    print_json(eval_expression_list(node).get());
  }

  void visit(const StatementNode&) { }

  void visit(const ForStatementNode&) { }

  void visit(const ForArrayStatementNode& node) {
    auto result = eval_expression_list(node.condition);
    if (!result->is_array()) {
      throw_renderer_error("object must be an array", node);
    }

    if (!current_loop_data->empty()) {
      auto tmp = *current_loop_data; // Because of clang-3
      (*current_loop_data)["parent"] = std::move(tmp);
    }

    for (auto it = result->begin(); it != result->end(); ++it) {
      json_loop_data[static_cast<std::string>(node.value)] = *it;

      size_t index = std::distance(result->begin(), it);
      (*current_loop_data)["index"] = index;
      (*current_loop_data)["index1"] = index + 1;
      (*current_loop_data)["is_first"] = (index == 0);
      (*current_loop_data)["is_last"] = (index == result->size() - 1);

      node.body.accept(*this);
    }

    json_loop_data[static_cast<std::string>(node.value)].clear();
    if (!(*current_loop_data)["parent"].empty()) {
      auto tmp = (*current_loop_data)["parent"];
      *current_loop_data = std::move(tmp);
    } else {
      current_loop_data = &json_loop_data["loop"];
    }
  }

  void visit(const ForObjectStatementNode& node) {
    auto result = eval_expression_list(node.condition);
    if (!result->is_object()) {
      throw_renderer_error("object must be an object", node);
    }

    if (!current_loop_data->empty()) {
      (*current_loop_data)["parent"] = std::move(*current_loop_data);
    }

    for (auto it = result->begin(); it != result->end(); ++it) {
      json_loop_data[static_cast<std::string>(node.key)] = it.key();
      json_loop_data[static_cast<std::string>(node.value)] = it.value();

      size_t index = std::distance(result->begin(), it);
      (*current_loop_data)["index"] = index;
      (*current_loop_data)["index1"] = index + 1;
      (*current_loop_data)["is_first"] = (index == 0);
      (*current_loop_data)["is_last"] = (index == result->size() - 1);

      node.body.accept(*this);
    }

    json_loop_data[static_cast<std::string>(node.key)].clear();
    json_loop_data[static_cast<std::string>(node.value)].clear();
    if (!(*current_loop_data)["parent"].empty()) {
      *current_loop_data = std::move((*current_loop_data)["parent"]);
    } else {
      current_loop_data = &json_loop_data["loop"];
    }
  }

  void visit(const IfStatementNode& node) {
    auto result = eval_expression_list(node.condition);
    if (truthy(result.get())) {
      node.true_statement.accept(*this);
    } else if (node.has_false_statement) {
      node.false_statement.accept(*this);
    }
  }

  void visit(const IncludeStatementNode& node) {
    auto sub_renderer = Renderer(config, template_storage, function_storage);
    auto included_template_it = template_storage.find(node.file);

    if (included_template_it != template_storage.end()) {
      sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_loop_data);
    } else if (config.throw_at_missing_includes) {
      throw_renderer_error("include '" + node.file + "' not found", node);
    }
  }

public:
  Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage)
      : config(config), template_storage(template_storage), function_storage(function_storage) { }

  void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) {
    output_stream = &os;
    current_template = &tmpl;
    json_input = &data;
    if (loop_data) {
      json_loop_data = *loop_data;
    }

    current_template->root.accept(*this);

    json_tmp_stack.clear();
  }
};

} // namespace inja

#endif // INCLUDE_INJA_RENDERER_HPP_

// #include "string_view.hpp"

// #include "template.hpp"

// #include "utils.hpp"


namespace inja {

using json = nlohmann::json;

/*!
 * \brief Class for changing the configuration.
 */
class Environment {
  std::string input_path;
  std::string output_path;

  LexerConfig lexer_config;
  ParserConfig parser_config;
  RenderConfig render_config;

  FunctionStorage function_storage;
  TemplateStorage template_storage;

public:
  Environment() : Environment("") {}

  explicit Environment(const std::string &global_path) : input_path(global_path), output_path(global_path) {}

  Environment(const std::string &input_path, const std::string &output_path)
      : input_path(input_path), output_path(output_path) {}

  /// Sets the opener and closer for template statements
  void set_statement(const std::string &open, const std::string &close) {
    lexer_config.statement_open = open;
    lexer_config.statement_open_no_lstrip = open + "+";
    lexer_config.statement_open_force_lstrip = open + "-";
    lexer_config.statement_close = close;
    lexer_config.statement_close_force_rstrip = "-" + close;
    lexer_config.update_open_chars();
  }

  /// Sets the opener for template line statements
  void set_line_statement(const std::string &open) {
    lexer_config.line_statement = open;
    lexer_config.update_open_chars();
  }

  /// Sets the opener and closer for template expressions
  void set_expression(const std::string &open, const std::string &close) {
    lexer_config.expression_open = open;
    lexer_config.expression_close = close;
    lexer_config.update_open_chars();
  }

  /// Sets the opener and closer for template comments
  void set_comment(const std::string &open, const std::string &close) {
    lexer_config.comment_open = open;
    lexer_config.comment_close = close;
    lexer_config.update_open_chars();
  }

  /// Sets whether to remove the first newline after a block
  void set_trim_blocks(bool trim_blocks) {
    lexer_config.trim_blocks = trim_blocks;
  }

  /// Sets whether to strip the spaces and tabs from the start of a line to a block
  void set_lstrip_blocks(bool lstrip_blocks) {
    lexer_config.lstrip_blocks = lstrip_blocks;
  }

  /// Sets the element notation syntax
  void set_search_included_templates_in_files(bool search_in_files) {
    parser_config.search_included_templates_in_files = search_in_files;
  }

  /// Sets whether a missing include will throw an error
  void set_throw_at_missing_includes(bool will_throw) {
    render_config.throw_at_missing_includes = will_throw;
  }

  Template parse(nonstd::string_view input) {
    Parser parser(parser_config, lexer_config, template_storage, function_storage);
    return parser.parse(input);
  }

  Template parse_template(const std::string &filename) {
    Parser parser(parser_config, lexer_config, template_storage, function_storage);
    auto result = Template(parser.load_file(input_path + static_cast<std::string>(filename)));
    parser.parse_into_template(result, input_path + static_cast<std::string>(filename));
    return result;
  }

  Template parse_file(const std::string &filename) {
    return parse_template(filename);
  }

  std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); }

  std::string render(const Template &tmpl, const json &data) {
    std::stringstream os;
    render_to(os, tmpl, data);
    return os.str();
  }

  std::string render_file(const std::string &filename, const json &data) {
    return render(parse_template(filename), data);
  }

  std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) {
    const json data = load_json(filename_data);
    return render_file(filename, data);
  }

  void write(const std::string &filename, const json &data, const std::string &filename_out) {
    std::ofstream file(output_path + filename_out);
    file << render_file(filename, data);
    file.close();
  }

  void write(const Template &temp, const json &data, const std::string &filename_out) {
    std::ofstream file(output_path + filename_out);
    file << render(temp, data);
    file.close();
  }

  void write_with_json_file(const std::string &filename, const std::string &filename_data,
                            const std::string &filename_out) {
    const json data = load_json(filename_data);
    write(filename, data, filename_out);
  }

  void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) {
    const json data = load_json(filename_data);
    write(temp, data, filename_out);
  }

  std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) {
    Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data);
    return os;
  }

  std::string load_file(const std::string &filename) {
    Parser parser(parser_config, lexer_config, template_storage, function_storage);
    return parser.load_file(input_path + filename);
  }

  json load_json(const std::string &filename) {
    std::ifstream file;
    open_file_or_throw(input_path + filename, file);
    json j;
    file >> j;
    return j;
  }

  /*!
  @brief Adds a variadic callback
  */
  void add_callback(const std::string &name, const CallbackFunction &callback) {
    function_storage.add_callback(name, -1, callback);
  }

  /*!
  @brief Adds a callback with given number or arguments
  */
  void add_callback(const std::string &name, int num_args, const CallbackFunction &callback) {
    function_storage.add_callback(name, num_args, callback);
  }

  /** Includes a template with a given name into the environment.
   * Then, a template can be rendered in another template using the
   * include "<name>" syntax.
   */
  void include_template(const std::string &name, const Template &tmpl) {
    template_storage[name] = tmpl;
  }
};

/*!
@brief render with default settings to a string
*/
inline std::string render(nonstd::string_view input, const json &data) {
  return Environment().render(input, data);
}

/*!
@brief render with default settings to the given output stream
*/
inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) {
  Environment env;
  env.render_to(os, env.parse(input), data);
}

} // namespace inja

#endif // INCLUDE_INJA_ENVIRONMENT_HPP_

// #include "exceptions.hpp"

// #include "parser.hpp"

// #include "renderer.hpp"

// #include "string_view.hpp"

// #include "template.hpp"


#endif // INCLUDE_INJA_INJA_HPP_
